/*
  File: Fraction.java

  Originally written by Doug Lea and released into the public domain.
  This may be used for any purposes whatsoever without acknowledgment.
  Thanks for the assistance and support of Sun Microsystems Labs,
  and everyone contributing, testing, and using this code.

  History:
  Date       Who                What
  7Jul1998  dl               Create public version
  11Oct1999 dl               add hashCode
 */

package lib;

import java.io.Serializable;
import java.math.BigDecimal;
import java.math.BigInteger;


/**
 * An immutable class representing fractions as pairs of longs.
 * Fractions are always maintained in reduced form.
 **/
public class Fraction extends Number implements Cloneable, Comparable<Fraction>, Serializable {

	private static final long serialVersionUID = 1L;
	protected final BigInteger numerator;
	protected final BigInteger denominator;

	/** Return the numerator **/
	public final BigInteger getNumerator() {
		return numerator;
	}

	/** Return the denominator **/
	public final BigInteger getDenominator() {
		return denominator;
	}

	/** Create a Fraction equal in value to num / den **/
	public Fraction(long num, long den) {
		numerator = BigInteger.valueOf(num);
		denominator = BigInteger.valueOf(den);
	}
	
	public Fraction(BigInteger num, BigInteger den) {
		numerator = num;
		denominator = den;
	}

	/** Create a fraction with the same value as Fraction f **/
	public Fraction(Fraction f) {
		numerator = f.getNumerator();
		denominator = f.getDenominator();
	}
	
	/** Create a fraction with a numerator of n and a denominator of one**/
	public Fraction(BigInteger n) {
		this.numerator = n;
		this.denominator = BigInteger.ONE;
	}
	
	public Fraction(Number n) {
		this.numerator = BigInteger.valueOf(n.longValue());
		this.denominator = BigInteger.ONE;
	}
	
	/**
	 * Returns a new Fraction that is a normalized version of this one.
	 * If this fraction is already normalized, returns a new Fraction anyway. 
	 * @return
	 */
	public Fraction normalized() {
		BigInteger g = numerator.abs().gcd(denominator);
		return new Fraction(numerator.divide(g), denominator.divide(g));
	}

	public String toString() { 
		if (getDenominator().equals(BigInteger.ONE)) {
			return "" + getNumerator();
		} else {
			return getNumerator() + "/" + getDenominator();
		}
	}

	@Override
	public Object clone() {
		return new Fraction(this); 
	}

	/** Return the value of the Fraction as a double **/
	public BigDecimal asBigDecimal() { 
		return new BigDecimal(getNumerator()).divide(new BigDecimal(getDenominator()), BigDecimal.ROUND_HALF_UP);
	}

	/** return a Fraction representing the negated value of this Fraction **/
	public Fraction negative() {
		return new Fraction(getNumerator().negate(), getDenominator());
	}

	/** return a Fraction representing 1 / this Fraction **/
	public Fraction inverse() {
		return new Fraction(getDenominator(), getNumerator());
	}


	/** return a Fraction representing this Fraction plus b **/
	public Fraction plus(Fraction b) {
		return new Fraction(
			numerator.multiply(b.denominator).add(b.numerator.multiply(denominator)),
			getDenominator().multiply(b.getDenominator())).normalized();
	}

	/** return a Fraction representing this Fraction plus n **/
	public Fraction plus(long n) {
		return plus(new Fraction(n,1));
	}

	/** return a Fraction representing this Fraction minus b **/
	public Fraction minus(Fraction b) {
		return plus(b.negative());
	}

	/** return a Fraction representing this Fraction minus n **/
	public Fraction minus(long n) {
		return plus(-n);
	}


	/** return a Fraction representing this Fraction times b **/
	public Fraction times(Fraction b) {
		return new Fraction(numerator.multiply(b.numerator), denominator.multiply(b.denominator));
	}

	/** return a Fraction representing this Fraction times n **/
	public Fraction times(long n) {
		return times(new Fraction(n, 1));
	}

	/** return a Fraction representing this Fraction divided by b **/
	public Fraction dividedBy(Fraction b) {
		return new Fraction(numerator.multiply(b.denominator), denominator.multiply(b.numerator));
	}

	/** return a Fraction representing this Fraction divided by n **/
	public Fraction dividedBy(long n) {
		return dividedBy(new Fraction(n,1));
	}
	
	/**
	 * Useful in continues fractions.
	 * @param f a Fraction to replace the denominator of this Fraction with
	 * @return a new Fraction with the same numerator  
	 */
	public Fraction replaceDenominatorWith(Fraction f) {
		return new Fraction(this.numerator.multiply(f.denominator), f.numerator);
	}

	/** return a number less, equal, or greater than zero
	 * reflecting whether this Fraction is less, equal or greater than 
	 * the value of Fraction other.
	 **/
	public int compareTo(Fraction other) {
		BigInteger l = numerator.multiply(other.denominator);
		BigInteger r = denominator.multiply(other.numerator);
		return (l.compareTo(r));
	}

	/** return a number less, equal, or greater than zero
	 * reflecting whether this Fraction is less, equal or greater than n.
	 **/

	public int compareTo(long n) {
		return compareTo(new Fraction(n,1));
	}
	
	public int compareTo(BigInteger other) {
		return compareTo(new Fraction(other,BigInteger.ONE));
	}
	
	public int compareTo(BigDecimal other) {
		return asBigDecimal().compareTo(other);
	}

	@Override
	public boolean equals(Object other) {
		if (other instanceof Fraction) {
			return compareTo((Fraction)other) == 0;
		} else if (other instanceof Number) {
			if (other instanceof BigInteger) {
				return compareTo((BigInteger)other) == 0;
			} else if (other instanceof BigDecimal) {
				return compareTo((BigDecimal)other) == 0;
			} else {
				return equals(new Fraction(BigInteger.valueOf(((Number)other).longValue()), BigInteger.ONE));
			}
		} else {
			throw new ClassCastException("don't know how to compare Fraction to object of type " + other.getClass().getSimpleName());
		}
	}

	public boolean equals(long n) {
		return compareTo(n) == 0;
	}

	public int hashCode() {
		Fraction normalized = normalized();
		return normalized.numerator.modPow(normalized.denominator, BigInteger.valueOf(Integer.MAX_VALUE)).intValue();
	}
	
	public boolean isInteger() {
		return normalized().getDenominator().equals(BigInteger.ONE);
	}

	@Override
	public int intValue() {
		if (asBigDecimal().compareTo(BigDecimal.valueOf(Integer.MAX_VALUE)) > 0) throw new NumberFormatException("value exceeds range of Integer");
		return asBigDecimal().intValue();
	}

	@Override
	public long longValue() {
		if (asBigDecimal().compareTo(BigDecimal.valueOf(Long.MAX_VALUE)) > 0) throw new NumberFormatException("value exceeds range of Long");
		return asBigDecimal().longValue();
	}

	@Override
	public float floatValue() {
		if (asBigDecimal().compareTo(BigDecimal.valueOf(Float.MAX_VALUE)) > 0) throw new NumberFormatException("value exceeds range of Float");
		return asBigDecimal().floatValue();
	}

	@Override
	public double doubleValue() {
		if (asBigDecimal().compareTo(BigDecimal.valueOf(Double.MAX_VALUE)) > 0) throw new NumberFormatException("value exceeds range of Double");
		return asBigDecimal().doubleValue();
	}

}
